<script>
  /**
   * @event {null} load
   * @event {null} error
   */

  /**
   * Specify the image source.
   */
  export let src = "";

  /**
   * Specify the image alt text.
   */
  export let alt = "";

  /**
   * Specify the aspect ratio for the image wrapper.
   * @type {"2x1" | "16x9" | "4x3" | "1x1" | "3x4" | "3x2" | "9x16" | "1x2"}
   */
  export let ratio = undefined;

  /**
   * Set to `true` when `loaded` is `true` and `error` is false.
   */
  export let loading = false;

  /**
   * Set to `true` when the image is loaded.
   */
  export let loaded = false;

  /**
   * Set to `true` if an error occurs when loading the image.
   */
  export let error = false;

  /**
   * Set to `true` to fade in the image on load.
   * The duration uses the `fast-02` value following Carbon guidelines on motion.
   */
  export let fadeIn = false;

  /**
   * Method invoked to load the image provided a `src` value
   * @type {(url?: string) => void}
   * @param {string} [url] - Optional URL to load. If not provided, uses the component's `src` prop
   * @example
   * ```svelte
   * <ImageLoader bind:this={loader} src="default.jpg" />
   * <button on:click={() => loader.loadImage("new-image.jpg")}>
   *   Load New Image
   * </button>
   * ```
   */
  export const loadImage = (url) => {
    if (image != null) image = null;
    loaded = false;
    error = false;
    image = new Image();
    image.src = url || src;
    image.onload = () => (loaded = true);
    image.onerror = () => (error = true);
  };

  import { createEventDispatcher, onMount } from "svelte";
  import { fade } from "svelte/transition";
  import AspectRatio from "../AspectRatio/AspectRatio.svelte";

  const dispatch = createEventDispatcher();

  // "fast-02" duration (ms) from Carbon motion recommended for fading micro-interactions
  const fast02 = 110;

  let image = null;

  $: loading = !loaded && !error;
  $: if (src && typeof window !== "undefined") loadImage();
  $: if (loaded) dispatch("load");
  $: if (error) dispatch("error");

  onMount(() => {
    return () => (image = null);
  });
</script>

{#if ratio === undefined}
  {#if loading}
    <slot name="loading" />
  {/if}
  {#key src}
    {#if loaded}
      <img
        style:width="100%"
        {...$$restProps}
        {src}
        {alt}
        transition:fade|local={{ duration: fadeIn ? fast02 : 0 }}
      />
    {/if}
  {/key}
  {#if error}
    <slot name="error" />
  {/if}
{:else}
  <AspectRatio {ratio}>
    {#if loading}
      <slot name="loading" />
    {/if}
    {#key src}
      {#if loaded}
        <img
          style:width="100%"
          {...$$restProps}
          {src}
          {alt}
          transition:fade|local={{ duration: fadeIn ? fast02 : 0 }}
        />
      {/if}
    {/key}
    {#if error}
      <slot name="error" />
    {/if}
  </AspectRatio>
{/if}
